package uva;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Date;
import java.util.Random;
import java.util.Vector;

import rice.environment.Environment;
import rice.p2p.commonapi.rawserialization.RawMessage;
import rice.pastry.NodeHandle;
import rice.pastry.NodeIdFactory;
import rice.pastry.PastryNode;
import rice.pastry.direct.DirectNodeHandle;
import rice.pastry.direct.EuclideanNetwork;
import rice.pastry.direct.NetworkSimulator;
import rice.pastry.routing.RouteSet;
import rice.pastry.routing.RoutingTable;

public class DirectRingRunner {

	// this will keep track of our applications
	Vector<PotentiallyMaliciousApp> apps = new Vector<PotentiallyMaliciousApp>();
	MaliciousNodes maliciousNodes;
	ArrayList<Integer> randomIndexes;

	public DirectRingRunner(int bindport, InetSocketAddress bootaddress, int numNodes, Environment env) throws Exception {
		randomIndexes = new ArrayList<Integer>();
		Random random = new Random(new Date().getTime());
		randomIndexes.add(random.nextInt(numNodes));

		boolean loop = true;
		while (loop) {
			int newRandom = random.nextInt(numNodes);
			if (!randomIndexes.contains(newRandom)) {
				randomIndexes.add(random.nextInt(numNodes));
				loop = false;
			}
		}

		maliciousNodes = MaliciousNodes.getInstance();
		// Generate the NodeIds Randomly
		NodeIdFactory nidFactory = new MaliciousRandomIdFactory(env);

		// create a new network
		NetworkSimulator<DirectNodeHandle, RawMessage> simulator = new EuclideanNetwork<DirectNodeHandle, RawMessage>(env);

		// set the max speed of the simulator so we can create several nodes in
		// a short amount of time
		// by default the simulator will advance very fast while we are slow on
		// the main() thread
		simulator.setMaxSpeed(1.0f);

		DirectMaliciousNodeFactory factory = new DirectMaliciousNodeFactory(nidFactory, simulator, env);

		// create the handle to boot off of
		NodeHandle bootHandle = null;

		for (int i = 0; i < numNodes; i++) {
			// System.out.println("creating node: " + i);
			PastryNode node = null;

			// if (i % 10 == 0) {
			if (randomIndexes.contains(i)) {
				// System.out.println("malicious node in index: " + i);
				node = factory.newNode(true);
				MaliciousNodes.getInstance().add(node.getLocalHandle());

				//System.out.println("creating node # " + i + ": >!" + node.getId().toStringFull() + "!<");
			}
			else {
				node = factory.newNode(false);
				//System.out.println("creating node # " + i + ": " + node.getId().toStringFull());
			}

			// construct a new MyApp
			PotentiallyMaliciousApp app = new PotentiallyMaliciousApp(node, false);

			apps.add(app);

			node.boot(bootHandle);

			// this way we can boot off the previous node
			bootHandle = node.getLocalHandle();

			synchronized (node) {
				while (!node.isReady() && !node.joinFailed()) {
					// delay so we don't busy-wait
					node.wait(500);

					// abort if can't join
					if (node.joinFailed()) {
						throw new IOException("Could not join the FreePastry ring.  Reason:" + node.joinFailedReason());
					}
				}
			}
		}

		System.out.println("number of malicious nodes: " + maliciousNodes.size() + " number non malicious nodes: " + (numNodes - maliciousNodes.size()));
		int iteration = 0;
		int currentCount = 0;
		int oldCount = -1;

		GraphViz oldGviz = null;
		GraphViz newGviz = null;
		do {
			System.out.println("iteration: " + iteration + " ");
			iteration++;
			// System.out.println(((PastryNode)apps.get(0).getNode()).getRoutingTable().printSelfFull());

			newGviz = new GraphViz("/home/alagenchev/graph.txt");

			for (PotentiallyMaliciousApp currentApp : apps) {

				// System.out.println(((PastryNode) currentApp.getNode()).getRoutingTable().printSelf());

				// System.out.println("for node: " + ((PastryNode)
				// currentApp.getNode()).getId().toStringFull() + " leafset: " +
				// ((PastryNode) currentApp.getNode()).getLeafSet().toString());

				newGviz.addEdges(currentApp.getNode());
				int nodeCount = MaliciousNodes.getInstance().countMaliciousNodes((PastryNode) currentApp.getNode(), false);

				if (nodeCount > maliciousNodes.size()) {
					System.out.println(((PastryNode) currentApp.getNode()).getRoutingTable().printSelf());
				}
			}

			// gviz.drawGraph();
			currentCount = newGviz.calculatePrintMaliciousNodes();
			int maxEdges = maliciousNodes.size() * (numNodes - maliciousNodes.size());

			if (currentCount > maxEdges) {
				newGviz.printAllMaliciousEdges();
			}

			if (currentCount < oldCount) {
				System.out.println("previous count was less than current count");

				// System.out.println("old gviz edges");
				//
				// oldGviz.printAllEdges();
				//
				// System.out.println("new gviz edges");
				//
				// newGviz.printAllEdges();
				// //printMissingEdges(oldGviz, newGviz);
				//
				// System.out.println("old gviz malicious edges");
				// oldGviz.printAllMaliciousEdges();
				//
				// System.out.println("new gviz malicious edges");
				// newGviz.printAllMaliciousEdges();
				//
				// oldGviz = newGviz;
				// printMissingEdges(newGviz, oldGviz);

				System.exit(0);
			}

			if (newGviz != null) {
				oldGviz = newGviz;
			}
			oldCount = currentCount;

			for (PotentiallyMaliciousApp currentApp : apps) {

				// System.out.println(((PastryNode) currentApp.getNode()).getRoutingTable().printSelf());

				// System.out.println("for node: " + ((PastryNode)
				// currentApp.getNode()).getId().toStringFull() + " leafset: " +
				// ((PastryNode) currentApp.getNode()).getLeafSet().toString());

				// maliciousNodes.countMaliciousNodes((PastryNode) currentApp.getNode(), true);
			}

			if (!MaliciousNodes.getInstance().getAreMaliciousNodesActive()) {
				System.out.println("activating malicious nodes");
				MaliciousNodes.getInstance().setAreMaliciousNodesActive(true);
			}

			// wait 10 seconds
			env.getTimeSource().sleep(10000);
			// don't limit the simulation speed anymore
			// simulator.setFullSpeed();
		}
		while (iteration < 10);
		// System.out.println("gviz malicious edges");

		// gviz.printAllMaliciousEdges();

		// MaliciousNodes.getInstance().setAreMaliciousNodesActive(true);
		// Thread.sleep(30000);

		// GraphViz gviz2 = new GraphViz("/home/alagenchev/graph2.txt");
		// for (PotentiallyMaliciousApp currentApp : apps) {

		// System.out.println(((PastryNode)
		// currentApp.getNode()).getRoutingTable().printSelf());

		// System.out.println("for node: " + ((PastryNode)
		// currentApp.getNode()).getId().toStringFull() + " leafset: " +
		// ((PastryNode) currentApp.getNode()).getLeafSet().toString());

		// gviz2.addEdges(currentApp.getNode());

		// }

		// gviz2.drawGraph();
		// gviz2.calculatePrintMaliciousNodes();

		// System.out.println("malicous nodes are: " +
		// MaliciousNodes.getInstance().size());
		// System.out.println("gviz2 malicious edges");

		// gviz2.printAllMaliciousEdges();
	}

	private void printMissingEdges(GraphViz oldGviz, GraphViz newGviz) {

		for (int i = 0; i < oldGviz.GetEdges().size(); i++) {
			if (!oldGviz.checkIfEdgeIsMalicious(oldGviz.GetEdges().get(i))) {
				oldGviz.GetEdges().remove(i);
				i--;
				continue;
			}

			int index = newGviz.hasMaliciousEdge(oldGviz.GetEdges().get(i));
			if (index >= 0) {
				oldGviz.GetEdges().remove(i);
				i--;
			}
			else {
				System.out.println("missing malicious edge:");
				if (oldGviz.GetEdges().get(i).getIsBiDirectional()) {
					System.out.println("\"" + oldGviz.GetEdges().get(i).getSource() + "\" -> \"" + oldGviz.GetEdges().get(i).getDestination() + "\"; -- bidirectional");
				}
				else {
					System.out.println("\"" + oldGviz.GetEdges().get(i).getSource() + "\" -> \"" + oldGviz.GetEdges().get(i).getDestination() + "\";");
				}
			}
		}
	}

	public static void main(String[] args) throws Exception {

		System.out.println("direct ring runner");
		// the number of nodes to use
		int numNodes = 300;

		// the port to use locally
		int myport = 9001;
		int bootport = 9001;

		String ipAddress = "192.168.1.102";

		// Loads pastry settings
		// Loads pastry settings, and sets up the Environment for simulation
		Environment env = Environment.directEnvironment();

		// disable the UPnP setting (in case you are testing this on a NATted
		// LAN)
		env.getParameters().setString("nat_search_policy", "never");

		// build the bootaddress from the command line args
		InetAddress bootaddr = InetAddress.getByName(ipAddress);

		InetSocketAddress bootaddress = new InetSocketAddress(bootaddr, bootport);

		// launch our node!
		DirectRingRunner runner = new DirectRingRunner(myport, bootaddress, numNodes, env);

	}
}
